package it.polimi.traveldream.ejb.eaos;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import it.polimi.traveldream.ejb.dtocreator.TravelPackageDTOCreator;
import it.polimi.traveldream.ejb.entities.Excursion;
import it.polimi.traveldream.ejb.entities.Hotel;
import it.polimi.traveldream.ejb.entities.Transport;
import it.polimi.traveldream.ejb.entities.Travelpackage;
import it.polimi.traveldream.ejbclient.dtos.ExcursionDTO;
import it.polimi.traveldream.ejbclient.dtos.HotelDTO;
import it.polimi.traveldream.ejbclient.dtos.ParticipationDTO;
import it.polimi.traveldream.ejbclient.dtos.TransportDTO;
import it.polimi.traveldream.ejbclient.dtos.TravelPackageDTO;
import it.polimi.traveldream.ejbclient.eaos.ParticipationServiceEAO;
import it.polimi.traveldream.ejbclient.eaos.TravelPackageServiceEAO;
import it.polimi.traveldream.utils.enums.PackageSearchType;
import it.polimi.traveldream.utils.enums.UserRole;
import it.polimi.traveldream.utils.exceptions.JustInUseException;
import it.polimi.traveldream.utils.exceptions.NotSuchProductException;

import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
 * Session Bean implementation class TravelPackageService
 */
@Stateless
public class TravelPackageService implements TravelPackageServiceEAO {
	@PersistenceContext
	private EntityManager entityManager;
	@EJB
	private ParticipationServiceEAO participationService;
	private final int CONDITION_OVERPAST = 0;
	private final int CONDITION_AVAILABLE = 1;
	private final int CONDITION_NOT_EXPIRED = 2;
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public List<TravelPackageDTO> searchPackage(PackageSearchType type, String keyword)
			throws NotSuchProductException {
		switch(type) {
		case ID:
			try{
				int packegeId = Integer.parseInt(keyword);
				List<TravelPackageDTO> packages = new ArrayList<TravelPackageDTO>();
				TravelPackageDTO travelPackage = getPackage(packegeId);
				if(!travelPackage.isOverpast()) {
					packages.add(travelPackage);
					return packages;
				} else throw new NotSuchProductException();
			} catch(NumberFormatException e) {
				throw new NotSuchProductException();
			}
		case PLACE:
			if(!keyword.isEmpty()) {
				List<Travelpackage> travelPackages = new ArrayList<Travelpackage>();
				travelPackages.addAll(fetchTravelPackagesByLocality(keyword));
				if(!travelPackages.isEmpty()) {
					return TravelPackageDTOCreator.createTravelPackageList(travelPackages);
				}
			}
		default:
			throw new NotSuchProductException();
		}
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	@SuppressWarnings("unchecked")
	public List<TravelPackageDTO> getAvailablePackages() {
		Query q = entityManager.createNamedQuery(Travelpackage.FIND_PACKAGES_AVAILABLE);
		return TravelPackageDTOCreator.createTravelPackageList(q.getResultList());
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@SuppressWarnings("unchecked")
	@Override
	public List<TravelPackageDTO> getPackages(boolean overpast) {
		Query q = entityManager.createNamedQuery(Travelpackage.FIND_PACKAGES);
		q.setParameter("overpast", overpast);
		return TravelPackageDTOCreator.createTravelPackageList(q.getResultList());
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public TravelPackageDTO getPackage(int packageId) throws NotSuchProductException{
		Travelpackage travelPackage = entityManager.find(Travelpackage.class, packageId);
		if(travelPackage != null) {
			return TravelPackageDTOCreator.createTravelPackage(travelPackage);
		} else throw new NotSuchProductException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean isAvailableFor(int packageId, int reservations) throws NotSuchProductException {
		return checkConditionOnProduct(CONDITION_AVAILABLE, packageId, reservations);
	}
	
	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean isOverpast(int packageId) throws NotSuchProductException {
		return checkConditionOnProduct(CONDITION_OVERPAST, packageId, null);
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean notExpired(int packageId) throws NotSuchProductException{
		return checkConditionOnProduct(CONDITION_NOT_EXPIRED, packageId, null);
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public int addPackage(TravelPackageDTO travelPackage) {
		Travelpackage newTravelPackage = new Travelpackage(travelPackage);
		newTravelPackage.setOverpast(false);
		newTravelPackage.setFeasibleReservations(Integer.MAX_VALUE);
		Date endDate = newTravelPackage.getEndTrip();
		newTravelPackage.setEndTrip(newTravelPackage.getStartTrip());
		newTravelPackage.setStartTrip(endDate);
		setHotelComposition(newTravelPackage,travelPackage.getHotels());
		setExcursionComposition(newTravelPackage,travelPackage.getExcursions());
		setTransportComposition(newTravelPackage,travelPackage.getTransports());
		entityManager.persist(newTravelPackage);
		entityManager.flush();
		return newTravelPackage.getId();
	}
	
	@RolesAllowed({UserRole._EMPLOYEE})
	@Override
	public void deletePredefinedPackage(int packageId)
			throws NotSuchProductException, JustInUseException{
		Travelpackage travelPackage = entityManager.find(Travelpackage.class, packageId);
		entityManager.refresh(travelPackage);
		if(travelPackage != null) {
			if(!justParticipatedPackage(packageId) && !travelPackage.getOverpast()) {
				entityManager.remove(travelPackage);
				for(Hotel h : travelPackage.getHotels()) {
					entityManager.refresh(h);
				}
				for(Transport t : travelPackage.getTransports()) {
					entityManager.refresh(t);
				}
				for(Excursion e : travelPackage.getExcursions()) {
					entityManager.refresh(e);
				}
				entityManager.flush();
			} else throw new JustInUseException();
		} else throw new NotSuchProductException();
	}

	@RolesAllowed({UserRole._CUSTOMER,UserRole._EMPLOYEE})
	@Override
	public boolean justParticipatedPackage(int packageId) {
		List<ParticipationDTO> participations;
		try {
			participations = participationService.searchParticipations(packageId, false);
			if(participations != null) {
				return !participations.isEmpty();
			}
		} catch (NotSuchProductException e) {
			return false;
		}
		return false;
	}

	private void setHotelComposition(Travelpackage newTravelPackage, List<HotelDTO> hotelDTOs) {
		List<Hotel> hotels = new ArrayList<Hotel>();
		Hotel hotel;
		int feasibleReservations;
		Date endDate;
		Date startDate;
		for(HotelDTO h : hotelDTOs) {
			hotel = entityManager.find(Hotel.class, h.getId());
			hotels.add(hotel);
			feasibleReservations = hotel.getMaxAvailability() - hotel.getReservations();
			if(feasibleReservations < newTravelPackage.getFeasibleReservations()) {
				newTravelPackage.setFeasibleReservations(feasibleReservations);
			}
			endDate = hotel.getEndTrip();
			if(endDate.after(newTravelPackage.getEndTrip())) {
				newTravelPackage.setEndTrip(endDate);
			}
			startDate = hotel.getStartTrip();
			if(startDate.before(newTravelPackage.getStartTrip())) {
				newTravelPackage.setStartTrip(startDate);
			}
			List<Travelpackage> travelPackages = hotel.getTravelpackages();
			travelPackages.add(newTravelPackage);
			hotel.setTravelpackages(travelPackages);
		}
		newTravelPackage.setHotels(hotels);
	}
	
	private void setExcursionComposition(Travelpackage newTravelPackage, List<ExcursionDTO> excursionDTOs) {
		List<Excursion> excursions = new ArrayList<Excursion>();
		Excursion excursion;
		int feasibleReservations;
		Date endDate;
		Date startDate;
		for(ExcursionDTO e : excursionDTOs) {
			excursion = entityManager.find(Excursion.class, e.getId());
			excursions.add(excursion);
			feasibleReservations = excursion.getMaxAvailability() - excursion.getReservations();
			if(feasibleReservations < newTravelPackage.getFeasibleReservations()) {
				newTravelPackage.setFeasibleReservations(feasibleReservations);
			}
			endDate = excursion.getEndTrip();
			if(endDate.after(newTravelPackage.getEndTrip())) {
				newTravelPackage.setEndTrip(endDate);
			}
			startDate = excursion.getStartTrip();
			if(startDate.before(newTravelPackage.getStartTrip())) {
				newTravelPackage.setStartTrip(startDate);
			}
			List<Travelpackage> travelPackages = excursion.getTravelpackages();
			travelPackages.add(newTravelPackage);
			excursion.setTravelpackages(travelPackages);
		}
		newTravelPackage.setExcursions(excursions);
	}
	
	private void setTransportComposition(Travelpackage newTravelPackage, List<TransportDTO> transportDTOs) {
		List<Transport> transports = new ArrayList<Transport>();
		Transport transport;
		int feasibleReservations;
		Date endDate;
		Date startDate;
		for(TransportDTO t : transportDTOs) {
			transport = entityManager.find(Transport.class, t.getId());
			transports.add(transport);
			feasibleReservations = transport.getMaxAvailability() - transport.getReservations();
			if(feasibleReservations < newTravelPackage.getFeasibleReservations()) {
				newTravelPackage.setFeasibleReservations(feasibleReservations);
			}
			endDate = transport.getEndTrip();
			if(endDate.after(newTravelPackage.getEndTrip())) {
				newTravelPackage.setEndTrip(endDate);
			}
			startDate = transport.getStartTrip();
			if(startDate.before(newTravelPackage.getStartTrip())) {
				newTravelPackage.setStartTrip(startDate);
			}
			List<Travelpackage> travelPackages = transport.getTravelpackages();
			travelPackages.add(newTravelPackage);
			transport.setTravelpackages(travelPackages);
		}
		newTravelPackage.setTransports(transports);
	}

	@SuppressWarnings("unchecked")
	private Set<Travelpackage> fetchTravelPackagesByLocality(String keyword) {
		Set<Travelpackage> travelPackages = new HashSet<Travelpackage>();
		Query q = entityManager.createNamedQuery(Travelpackage.FIND_PACKAGES_BY_HOTEL_PLACE);
		q.setParameter("place", "%" + keyword + "%");
		List<Travelpackage> hotelPackages = (List<Travelpackage>) q.getResultList();
		q = entityManager.createNamedQuery(Travelpackage.FIND_PACKAGES_BY_TRANSPORT_PLACE);
		q.setParameter("place", "%" + keyword + "%");
		List<Travelpackage> transportPackages = (List<Travelpackage>) q.getResultList();
		q = entityManager.createNamedQuery(Travelpackage.FIND_PACKAGES_BY_EXCURSION_PLACE);
		q.setParameter("place", "%" + keyword + "%");
		List<Travelpackage> excursionPackages = (List<Travelpackage>) q.getResultList();
		travelPackages.addAll(hotelPackages);
		travelPackages.addAll(transportPackages);
		transportPackages.addAll(excursionPackages);
		return travelPackages;
	}
	
	private boolean checkConditionOnProduct(int condition, int packageId, Integer reservations)
			throws NotSuchProductException {
		Travelpackage travelPackage = entityManager.find(Travelpackage.class, packageId);
		if(travelPackage != null) {
			switch(condition) {
			case CONDITION_AVAILABLE:
				return travelPackage.getFeasibleReservations() >= reservations;
			case CONDITION_OVERPAST:
				return travelPackage.getOverpast();
			case CONDITION_NOT_EXPIRED:
				Date currentDate = new Date();
				return currentDate.before(travelPackage.getExpiry());
			default:
				throw new NotSuchProductException();
			}
		} else throw new NotSuchProductException();
	}
	
}
